#ifndef logger_h
#define logger_h

/**
  * @file logger.h
  * @author Julien CLEMENT
  * @brief A lightweight logger mechanism with multiple levels of logging. 
  * Creation date : 10/06/2011
  */

#include <stdlib.h>
#include <ostream>
using namespace std;

/**
  * The file where we log.
  */
#ifndef LOGFILE
  #define LOGFILE "./logger.log"
#endif

/**
  * The output handler.
  */
extern ostream* logfile;

/**
  * @enum LogLevel
  * The available log levels.
  */
typedef enum {
  LOGLEVEL_INFO    = 0, //< Information, always logged
  LOGLEVEL_DEBUG   = 1, //< Debug, display only if NDEBUG is not set
  LOGLEVEL_WARNING = 2, //< Warning, always logged
  LOGLEVEL_ERROR   = 3, //< Error, always logged
  LOGLEVEL_FATAL   = 4  //< Fatal, always logged, triggers an exit
} LogLevel;

/**
  * File handler initialization function.
  * @p logfile filename of the logfile. If null or empty, use LOGFILE.
  *            In case of failure, log a fatal message (and hence call
  *            logger_quit, which in turn will call logger_quit_callback).
  * @p user_callback callback called at exit, implemented by the user. This
  * callback takes the error state in argument. See the system function exit()
  * for more information.
  */
void logger_init (const char* logfile, void (*user_callback)(int));

/**
  * Termination function.
  * Calls the user defined quit function if any (logger_quit_callback) *after* 
  * doing the other logger-specific quit operations.
  */
void logger_quit ();

/**
  * User-defined termination function.
  * This function is allowed to call a system exit function as it is called
  * at the very end of logger_quit.
  * BEWARE : no logging is allowed in this function.
  */
typedef void (*logger_quit_callback)(int) ;
extern logger_quit_callback user_callback;

/**
  * Activate or deactivate flush after each log entry.
  * Deactivating flush improves performance, but if a crash occurs
  * the file may not be flushed on the disk and some log information
  * may be lost. Use with care.
  * Default is to flush.
  */
#define logger_toggle_flush(state) flush_log = (state);

/**
  * The various strings used in the display.
  */
extern const char* logger_lvl_strings[];

/**
  * Used internally to know if we're starting a new log line or not.
  */
extern bool inside_line;

/**
  * Used internally to know if a flush is needed after each log.
  */
extern bool flush_log;

/**
  * Used internally to store the exit code.
  */
extern int exit_status;

/**
  * The toplevel logging macro.
  */
#define LOG_BASE(loglevel,str,newline) \
  if (!inside_line) { \
    *logfile << logger_lvl_strings[loglevel]; \
    *logfile << __FILE__ << " [" << __LINE__ << "] : "; \
  } \
  *logfile << str; \
  inside_line = !newline; \
  if (newline) { \
    *logfile << "\n"; \
  } \
  if (flush_log) { \
    logfile->flush(); \
  }

/**
  * Per-level logging macros.
  */
/**
  * With trailing newline.
  */
// Info.
#define LOG_INFO(str)     LOG_BASE(LOGLEVEL_INFO,str,true)
// Debug.
#ifndef NDEBUG
#define LOG_DEBUG(str)    LOG_BASE(LOGLEVEL_DEBUG,str,true)
#else
#define LOG_DEBUG(str)
#endif
// Warning.
#define LOG_WARNING(str)  LOG_BASE(LOGLEVEL_WARNING,str,true)
// Error.
#define LOG_ERROR(str)    LOG_BASE(LOGLEVEL_ERROR,str,true)
// Fatal : note that this will call the logger_quit() function.
#define LOG_FATAL(str) \
  LOG_BASE(LOGLEVEL_FATAL,str,true) \
  exit_status = EXIT_FAILURE; \
  logger_quit();

/**
  * Without trailing newline.
  */
// Info.
#define LOGN_INFO(str)     LOG_BASE(LOGLEVEL_INFO,str,false)
// Debug.
#ifndef NDEBUG
#define LOGN_DEBUG(str)    LOG_BASE(LOGLEVEL_DEBUG,str,false)
#else
#define LOGN_DEBUG(str)
#endif
// Warning.
#define LOGN_WARNING(str)  LOG_BASE(LOGLEVEL_WARNING,str,false)
// Error.
#define LOGN_ERROR(str)    LOG_BASE(LOGLEVEL_ERROR,str,false)

/**
  * Practical macros.
  */
// No suffix = info.
#define LOG(str)  LOG_INFO(str)
#define LOGN(str) LOGN_INFO(str)

// Log a variable and its value.
#define LOG_VAR(var) \
  LOGN_DEBUG(#var); \
  LOGN_DEBUG(" = "); \
  LOG_DEBUG(var);

#define LOGN_VAR(var)

#endif

